package hydrap2p.search.ubuntu;

import hydrap2p.download.Source;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.library.HashData;
import hydrap2p.library.Range;
import hydrap2p.logging.Level;
import hydrap2p.search.SearchQuery;
import hydrap2p.search.SearchResult;
import hydrap2p.search.Searcher;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.thrift.TException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Spawned for each new Search that is being run
 * @author Hufman
 *
 */
public class UbuntuSearchThread implements Runnable {

	private static final String mainmirror = "http://ubuntu.osuosl.org/releases/";
	private static final int DNSLOOKUPTIMEOUT=30000;	// 30 seconds timeout to look up all of the mirrors
	private int searchid;
	private SearchQuery query;
	
	private static final Pattern ReleaseRegex=Pattern.compile("<img\\s+src=\"/icons/folder\\.gif\"\\s+alt=\"\\[DIR\\]\">\\s*<a\\s+href=\"(\\S+)/\">\\S+/</a>");
	private static final Pattern FileRegex=Pattern.compile("<img\\s+src=\"/icons/.+\\.gif\"\\s+alt=\"\\[...\\]\">\\s*<a\\s+href=\"(\\S+)\">\\S+</a>");
	
	private boolean running;
	
	
	public UbuntuSearchThread(int searchid, SearchQuery query)
	{
		this.searchid=searchid;
		this.query=query;
	}
	
	/**
	 * Get the list of releases the pull information from each
	 */
	@Override
	public void run() {
		running=true;
		List<String> releases=listReleases(mainmirror);
		for (String release : releases)
		{
			if (running)
				addRelease(release);
		}
		releases=listReleases(mainmirror+"/kubuntu");
		for (String release : releases)
		{
			if (running)
				addRelease("kubuntu/" + release);
		}
		shutdown();
	}
	
	public void shutdown() {
		running=false;
		try {
	    	Searcher.Client searcher=ServiceConnector.getSearcher();
	    	if (searcher!=null)
	    		searcher.searchFinished(searchid, UbuntuSearcher.SEARCHPROVIDERNAME);
		} catch (TException e) {
			
		}
	}
	
	/**
	 * Return a list of releases on the mirror
	 * @return
	 */
	private List<String> listReleases(String website) {
		List<String> ret=new LinkedList<String>();

		URL url;
		try {
			url=new URL(website);
		} catch (MalformedURLException e) {
			// Fail to parse the URL, highly unlikely
			UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not parse mirror's url: "+website);
			e.printStackTrace();
			return ret;
		}
		
		HttpURLConnection conn;
		try {
			conn = (HttpURLConnection)url.openConnection();
		} catch (IOException e) {
			// Fail to connect to the server, don't return any results
			UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not connect to a mirror to get a list of releases");
			return ret;
		}
		
		InputStream input;
		try {
			conn.connect();
			input = new BufferedInputStream(conn.getInputStream());
		} catch (IOException e) {
			// Fail to read from the server
			UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not get a list of releases");
			return ret;
		}
		
		BufferedReader reader=new BufferedReader(new InputStreamReader(input));
		String line=null;
		do
		{
			try {
				line=reader.readLine();
			} catch (IOException e) {
				// Interrupted
				UbuntuLogging.log(Level.ERROR, "Ubuntu Search", "Interrupted while getting a list of releases");
				return ret;
			}
			
			if (line!=null && line.contains("<img src=\"/icons/folder.gif\" alt=\"[DIR]\">") 
					&& !line.contains("releases") 
					&& !line.contains("include") 
					&& !line.contains("kubuntu")
					&& !line.contains("edubuntu")
				)
			{
				Matcher matches=ReleaseRegex.matcher(line);
				if (matches.find())
				{
					ret.add(matches.group(1));
				}
			}
		} while (line!=null);
		
		return ret;
	}
	
	/**
	 * Look through a specific release and add any files that are found
	 * @param release
	 */
	private void addRelease(String release)
	{
		List<Thread> children=new LinkedList<Thread>();
		
		UbuntuLogging.log(Level.DEBUG, "Ubuntu Search", "Looking for downloads in Ubuntu "+release);
		
		URL url;
		try {
			url=new URL(mainmirror+"/"+release);
		} catch (MalformedURLException e) {
			// Fail to parse the URL, highly unlikely
			UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not parse release's url: "+mainmirror+"/"+release);
			e.printStackTrace();
			return;
		}
		
		HttpURLConnection conn;
		try {
			conn = (HttpURLConnection)url.openConnection();
		} catch (IOException e) {
			// Fail to connect to the server, don't return any results
			UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not browse files from release "+release);
			return;
		}
		
		InputStream input;
		try {
			conn.connect();
			input = new BufferedInputStream(conn.getInputStream());
		} catch (IOException e) {
			// Fail to read from the server
			UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not get a list of files from release "+release);
			return;
		}
		
		BufferedReader reader=new BufferedReader(new InputStreamReader(input));
		String line=null;
		do
		{
			try {
				line=reader.readLine();
			} catch (IOException e) {
				// Interrupted
				UbuntuLogging.log(Level.ERROR, "Ubuntu Search", "Interrupted while getting a list of files from release "+release);
				return;
			}
			
			if (line!=null && line.contains("<img src=\"/icons/"))
			{
				Matcher matches=FileRegex.matcher(line);
				if (matches.find())
				{
					String filename=matches.group(1);
					
					if (filename.endsWith(".metalink"))
					{
						String name=filename.substring(0, filename.indexOf(".metalink"))+".iso";
						
						boolean doesmatch=false;
						String[] keywords=query.keywords.trim().split("\\s+");
						for (String keyword : keywords)
						{
							UbuntuLogging.log(Level.VERBOSE, "Ubuntu Search", "Matching keyword "+keyword+" against filename "+filename+" or release "+release);
							if (filename.contains(keyword) || release.contains(keyword))
								doesmatch=true;
							if (keyword.equals("x86") && filename.contains("i386"))
								doesmatch=true;
							if (keyword.equals("x64") && filename.contains("amd64"))
								doesmatch=true;
						}
						
						if (doesmatch)
						{
							try {
								Thread temp=new MetalinkParser(name,release+"/"+filename);
								temp.start();
								children.add(temp);
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
		} while (line!=null);
		
		for (Thread temp:children)
		{
			try {
				temp.join();
			} catch (InterruptedException e) {
				// who cares
			}
		}
	}
	
	private class MetalinkParser extends Thread
	{
		private String name;
		private String metalink;
		public MetalinkParser(String name, String metalink)
		{
			this.name=name;
			this.metalink=metalink;
		}
		
		public void run()
		{

			UbuntuLogging.log(Level.DEBUG, "Ubuntu Search", "Parsing sources from "+metalink);
			
			SearchResult result=new SearchResult();
			result.names=Arrays.asList(name);
			result.filenames=Arrays.asList(name);
			result.sources=new LinkedList<Source>();
			result.extra=new TreeMap<String, String>();
			result.hashes=new LinkedList<HashData>();
			
			URL url;
			try {
				url=new URL(mainmirror+"/"+metalink);
			} catch (MalformedURLException e) {
				// Fail to parse the URL, highly unlikely
				e.printStackTrace();
				return;
			}
			
			HttpURLConnection conn;
			try {
				conn = (HttpURLConnection)url.openConnection();
			} catch (IOException e) {
				// Fail to connect to the server, don't return any results
				UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not download "+metalink);
				return;
			}
			
			InputStream input;
			try {
				conn.connect();
				input = new BufferedInputStream(conn.getInputStream());
			} catch (IOException e) {
				// Fail to read from the server
				UbuntuLogging.log(Level.FATAL, "Ubuntu Search", "Could not parse information from "+metalink);
				return;
			}
		    DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
		    domFactory.setNamespaceAware(true); // never forget this!
		    DocumentBuilder builder;
		    Document doc;
			try {
				builder = domFactory.newDocumentBuilder();
			    doc=builder.parse(input);
			} catch (ParserConfigurationException e1) {
				UbuntuLogging.log(Level.MISHAP, "Ubuntu Search", "Could not create parser for release information from "+url.toString());
				return;
			} catch (SAXException e) {
				UbuntuLogging.log(Level.MISHAP, "Ubuntu Search", "Could not parse release information from "+url.toString());
				return;
			} catch (IOException e) {
				UbuntuLogging.log(Level.MISHAP, "Ubuntu Search", "Could not read release information from "+url.toString());
				return;
			}
		    
		    long size=-1;
		    NodeList sizes=doc.getElementsByTagName("size");
		    if (sizes.getLength()>0)
		    {
		    	Node item=sizes.item(0);
		    	size=Integer.parseInt(sizes.item(0).getFirstChild().getNodeValue());
		    }
		    if (size!=-1)
		    {
		    	result.size=size;
		    	result.filesizes=Arrays.asList(size);
		    }

		    NodeList hashes=doc.getElementsByTagName("hash");
		    if (hashes.getLength()>0)
		    {
		    	Node item=hashes.item(0);
		    	Node typenode=item.getAttributes().getNamedItem("type");
		    	String type=null;
		    	if (typenode.getNodeValue().equals("md5"))
		    		type="MD5";
		    	if (type!=null)
		    	{
		    		HashData temp=new HashData();
		    		temp.algorithm=type;
		    		temp.hashdata=item.getFirstChild().getNodeValue();
		    		temp.range=new Range();
		    		temp.range.start=0;
		    		temp.range.len=result.size;
		    		result.hashes.add(temp);
		    	}
		    }

			List<DNSLookupThread> threads=new LinkedList<DNSLookupThread>();
			
		    NodeList mirrors=doc.getElementsByTagName("url");
		    for (int i = 0; i < mirrors.getLength(); i++) {
		    	Node item=mirrors.item(i);
		    	NamedNodeMap attrs=item.getAttributes();
		    	if (attrs.getLength()==0)
		    		continue;
		    	Node has=attrs.getNamedItem("type");
		    	if (has==null)
		    		continue;
		    	
		    	String mirrorprotocol=has.getTextContent();
		    	String mirrorurl=item.getFirstChild().getNodeValue();
		    	//mirrorprotocol="http";
		    	//mirrorurl="http://ubuntu.osuosl.org/releases/9.10/ubuntu-9.10-alternate-amd64.iso";
				//UbuntuLogging.log(Level.DEBUG, "Ubuntu Search", "Found source from "+metalink+": "+mirrorprotocol+" - "+mirrorurl);
			
				if (mirrorprotocol.equals("http"))
				{
			    	url=null;
					try {
						url = new URL(mirrorurl);
					} catch (MalformedURLException e) {
						UbuntuLogging.log(Level.MISHAP, "Ubuntu Search - DNS Lookup", "Could not parse "+mirrorurl);
						continue;
					}
					
					DNSLookupThread temp=new DNSLookupThread(result.sources, url, size);
					temp.start();
					threads.add(temp);
				}
		    }
		    
		    long starttime=System.currentTimeMillis();
		    boolean stillrunning=true;
		    while (stillrunning && starttime+DNSLOOKUPTIMEOUT>System.currentTimeMillis())
		    {
		    	try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					break;
				}
				stillrunning=false;
				for (DNSLookupThread thread : threads)
				{
					stillrunning|=thread.isAlive();
				}
		    }
		    
			UbuntuLogging.log(Level.DEBUG, "Ubuntu Search", "Adding search result "+result.names.get(0));
			result.provider="Ubuntu";
		    try {
		    	Searcher.Client searcher=ServiceConnector.getSearcher();
		    	synchronized(result.sources)		// make sure any late DNSLookupThreads don't modify stuff
		    	{
			    	if (searcher!=null)
			    		searcher.addSearchResult(searchid, result);
			    	else
			    		UbuntuLogging.log(Level.ERROR, "Ubuntu Search", "Could not connect to Search Manager");
		    	}
			} catch (TException e) {
	    		UbuntuLogging.log(Level.ERROR, "Ubuntu Search", "Could not send search result for "+name+" to Search Manager");
			}
		}
		
		private class DNSLookupThread extends Thread
		{
			private final List<Source> sources;
			private final URL url;
			private final long size;
			public DNSLookupThread(List<Source> sources, URL url, long size)
			{
				this.sources=sources;
				this.url=url;
				this.size=size;
			}
			@Override
			public void run()
			{
				String[] ips=DNSLookup.getIP(url);
				if (ips!=null)
				{
					for (String ip:ips)
					{
						Source source=new Source();
						source.available=Arrays.asList(new Range(0,size));
						source.downloaded=new LinkedList<Range>();
						source.ip=ip;
						source.port=80;
						source.protocol="http";
						source.extra=new TreeMap<String,String>();
						source.extra.put("path",url.getPath());
						source.extra.put("extraheaders","Host");
						source.extra.put("Host",url.getHost());
						synchronized(sources)
						{
							sources.add(source);
						}
					}
				}
			}
		}
		
	}
}
